package com.sunday.common.mq.rabbit.study.spring.study;

import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.net.*;
import java.util.*;
import java.util.regex.Pattern;

import static java.util.Collections.emptyList;

/**
 * @Description: 获取IP地址
 * @Author: zsy
 * @Create: 2022/10/11 14:36
 */
@Slf4j
public class NetUtils {

    private static final Pattern IP_PATTERN = Pattern.compile("\\d{1,3}(\\.\\d{1,3}){3,5}$");

    private static volatile InetAddress LOCAL_ADDRESS = null;

    private static String ANYHOST_VALUE = "0.0.0.0";

    private static String LOCALHOST_VALUE = "127.0.0.1";

    /**
     * @Description: 从本地网卡找到第一个有效的IP
     * @Param: []
     * @Return: java.net.InetAddress
     * @Author: zsy
     * @Time: 2022/10/11 14:37
     */
    public static InetAddress getLocalAddress() {
        if (LOCAL_ADDRESS != null) {
            return LOCAL_ADDRESS;
        }
        InetAddress localAddress = getLocalAddress0();
        LOCAL_ADDRESS = localAddress;
        return localAddress;
    }

    /**
     * @Description: 获取网卡
     * @Param: []
     * @Return: java.net.InetAddress
     * @Author: zsy
     * @Time: 2022/10/11 14:37
     */
    private static InetAddress getLocalAddress0() {
        InetAddress localAddress = null;
        try {
            NetworkInterface networkInterface = findNetworkInterface();
            Enumeration<InetAddress> addresses = networkInterface.getInetAddresses();
            while (addresses.hasMoreElements()) {
                Optional<InetAddress> addressOp = toValidAddress(addresses.nextElement());
                if (addressOp.isPresent()) {
                    try {
                        if (addressOp.get().isReachable(100)) {
                            return addressOp.get();
                        }
                    } catch (IOException e) {
                        // ignore
                    }
                }
            }
        } catch (Throwable e) {
            log.warn("{}", e);
        }

        try {
            localAddress = InetAddress.getLocalHost();
            Optional<InetAddress> addressOp = toValidAddress(localAddress);
            if (addressOp.isPresent()) {
                return addressOp.get();
            }
        } catch (Throwable e) {
            log.warn("{}", e);
        }
        return localAddress;
    }

    /**
     * @Description: 获取网络接口
     * @Param: []
     * @Return: java.net.NetworkInterface
     * @Author: zsy
     * @Time: 2022/10/11 14:38
     */
    public static NetworkInterface findNetworkInterface() {
        List<NetworkInterface> validNetworkInterfaces = emptyList();
        try {
            validNetworkInterfaces = getValidNetworkInterfaces();
        } catch (Throwable e) {
            log.warn("{}", e);
        }

        NetworkInterface result = null;

        if (result == null) { // If not found, try to get the first one
            for (NetworkInterface networkInterface : validNetworkInterfaces) {
                Enumeration<InetAddress> addresses = networkInterface.getInetAddresses();
                while (addresses.hasMoreElements()) {
                    Optional<InetAddress> addressOp = toValidAddress(addresses.nextElement());
                    if (addressOp.isPresent()) {
                        try {
                            if (addressOp.get().isReachable(100)) {
                                result = networkInterface;
                                break;
                            }
                        } catch (IOException e) {
                            // ignore
                        }
                    }
                }
            }
        }

        if (result == null) {
            result = first(validNetworkInterfaces);
        }

        return result;
    }

    /**
     * @Description: 验证网卡有效性
     * @Param: [address]
     * @Return: java.util.Optional<java.net.InetAddress>
     * @Author: zsy
     * @Time: 2022/10/11 14:38
     */
    private static Optional<InetAddress> toValidAddress(InetAddress address) {
        if (address instanceof Inet6Address v6Address) {
            if (isPreferIPV6Address()) {
                return Optional.ofNullable(normalizeV6Address(v6Address));
            }
        }
        if (isValidV4Address(address)) {
            return Optional.of(address);
        }
        return Optional.empty();
    }

    /**
     * @Description: 获取有效的网络接口
     * @Param: []
     * @Return: java.util.List<java.net.NetworkInterface>
     * @Author: zsy
     * @Time: 2022/10/11 14:38
     */
    private static List<NetworkInterface> getValidNetworkInterfaces() throws SocketException {
        List<NetworkInterface> validNetworkInterfaces = new LinkedList<>();
        Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
        while (interfaces.hasMoreElements()) {
            NetworkInterface networkInterface = interfaces.nextElement();
            if (ignoreNetworkInterface(networkInterface)) { // ignore
                continue;
            }
            validNetworkInterfaces.add(networkInterface);
        }
        return validNetworkInterfaces;
    }

    /**
     * @Description: 检查是否为ipv6地址
     * @Param: []
     * @Return: boolean true if it is reachable
     * @Author: zsy
     * @Time: 2022/10/11 14:38
     */
    static boolean isPreferIPV6Address() {
        return Boolean.getBoolean("java.net.preferIPv6Addresses");
    }

    /**
     * @Description: 规范化ipv6地址，将范围名称转换为范围id.
     * <p>
     * e.g.
     * convert
     * fe80:0:0:0:894:aeec:f37d:23e1%en0
     * to
     * fe80:0:0:0:894:aeec:f37d:23e1%5
     * <p>
     * The %5 after ipv6 address is called scope id.
     * see java doc of {@link Inet6Address} for more details.
     * @Param: [address the input address]
     * @Return: java.net.InetAddress the normalized address, with scope id converted to int
     * @Author: zsy
     * @Time: 2022/10/11 14:39
     */
    static InetAddress normalizeV6Address(Inet6Address address) {
        String addr = address.getHostAddress();
        int i = addr.lastIndexOf('%');
        if (i > 0) {
            try {
                return InetAddress.getByName(addr.substring(0, i) + '%' + address.getScopeId());
            } catch (UnknownHostException e) {
                // ignore
                log.debug("Unknown IPV6 address: ", e);
            }
        }
        return address;
    }

    /**
     * @Description: 检查是否为ipv4地址
     * @Param: [address]
     * @Return: boolean true if it is reachable
     * @Author: zsy
     * @Time: 2022/10/11 14:40
     */
    static boolean isValidV4Address(InetAddress address) {
        if (address == null || address.isLoopbackAddress()) {
            return false;
        }

        String name = address.getHostAddress();
        return (name != null
                && IP_PATTERN.matcher(name).matches()
                && !ANYHOST_VALUE.equals(name)
                && !LOCALHOST_VALUE.equals(name));
    }

    /**
     * @Description: 忽略网络接口
     * @Param: [networkInterface]
     * @Return: boolean
     * @Author: zsy
     * @Time: 2022/10/11 14:40
     */
    private static boolean ignoreNetworkInterface(NetworkInterface networkInterface) throws SocketException {
        return networkInterface == null
                //返回是否网络接口是一个回环接口
                || networkInterface.isLoopback()
                //返回是否该接口是一种虚拟接口
                || networkInterface.isVirtual()
                //返回一个网络接口是否运行
                || !networkInterface.isUp();
    }

    /**
     * @Description: 获取指定集合中的第一个元素
     * @Param: [values]
     * @Return: T
     * @Author: zsy
     * @Time: 2022/10/11 14:40
     */
    public static <T> T first(Collection<T> values) {
        if (isEmpty(values)) {
            return null;
        }
        if (values instanceof List list) {
            return (T)list.get(0);
        } else {
            return values.iterator().next();
        }
    }

    /**
     * @Description: 检测集合是否为空
     * @Param: [collection]
     * @Return: boolean
     * @Author: zsy
     * @Time: 2022/10/11 14:40
     */
    public static boolean isEmpty(Collection<?> collection) {
        return collection == null || collection.isEmpty();
    }

}
